package org.zjvis.graph.analysis.service.util;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.zjvis.datascience.common.graph.vo.CategoryVO;
import org.zjvis.datascience.common.graph.vo.EdgeVO;
import org.zjvis.graph.analysis.service.exception.GraphAnalysisException;
import org.zjvis.graph.analysis.service.model.element.MetricResultManager;
import org.zjvis.graph.analysis.service.enums.ApiResultCode;

import java.sql.Types;
import java.text.DecimalFormat;
import java.util.*;

public class GraphUtil {
    public static String genCategoryId(Long gid, List<CategoryVO> categories){
        if (categories.size() == 0) {
            return String.join("_", "c", gid.toString(), "0");
        }
        List<Long> tmp = new ArrayList<>();
        for (CategoryVO c: categories){
            String cid = c.getId();
            String[] splitId = cid.split("_");
            Long id = Long.parseLong(splitId[splitId.length - 1]);
            tmp.add(id);
        }
        Long newId = Collections.max(tmp) + 1;
        return String.join("_", "c", gid.toString(), newId.toString());
    }

    public static String genEdgeId(Long gid, List<EdgeVO> edges){
        if (edges.size() == 0) {
            return String.join("_", "e", gid.toString(), "0");
        }
        List<Long> tmp = new ArrayList<>();
        for (EdgeVO e: edges){
            String eid = e.getId();
            String[] splitId = eid.split("_");
            Long id = Long.parseLong(splitId[splitId.length - 1]);
            tmp.add(id);
        }
        Long newId = Collections.max(tmp) + 1;
        return String.join("_", "e", gid.toString(), newId.toString());
    }

    public static String sqlType2JanusType(int type) {
        String typeString = "";
        switch (type) {
            case Types.VARCHAR:
            case Types.LONGVARCHAR:
                typeString = "String";
                break;
            case Types.CHAR:
                typeString = "Character";
                break;
            case Types.BOOLEAN:
                typeString = "Boolean";
                break;
            case Types.BINARY:
                typeString = "Byte";
                break;
            case Types.SMALLINT:
                typeString = "Short";
                break;
            case Types.INTEGER:
                typeString = "Integer";
                break;
            case Types.BIGINT:
                typeString = "Long";
                break;
            case Types.FLOAT:
                typeString = "Float";
                break;
            case Types.DOUBLE:
            case Types.NUMERIC:
                typeString = "Double";
                break;
            case Types.DATE:
                typeString = "Date";
                break;
            default:
                typeString = "";

        }
        return typeString;
    }

    public static Class<?> sqlType2TypeClass(int type) {
        Class<?> dataType;
        switch (type) {
            case Types.VARCHAR:
            case Types.LONGVARCHAR:
            case Types.CHAR:
            case Types.BOOLEAN:
                dataType = String.class;
                break;
            case Types.BINARY:
            case Types.SMALLINT:
            case Types.INTEGER:
            case Types.BIGINT:
                dataType = Long.class;
                break;
            case Types.FLOAT:
            case Types.DOUBLE:
            case Types.NUMERIC:
                dataType = Double.class;
                break;
            default:
                dataType = String.class;

        }
        return dataType;
    }

    public static int dataCleanType2sqlType(String totype) {
        int typeInt = Types.LONGVARCHAR;
        switch (totype) {
            case "decimal":
                typeInt = Types.NUMERIC;
                break;
            case "int":
                typeInt = Types.INTEGER;
                break;
            case "date":
                typeInt = Types.DATE;
                break;
            case "text":
            case "varchar":
                typeInt = Types.LONGVARCHAR;
                break;
        }
        return typeInt;
    }

    public static String getAliasName(String str) {
        //部分字段和janusgraph关键字冲突，需要微调
        String aliasName = str;
        switch (str) {
            case "id":
                aliasName = "_id_";
                break;
            case "label":
                aliasName = "_label_";

        }
        return aliasName;
    }

    public static Object parsePropertyValue(Object value, int type) {
        Object _value;
        switch (type) {
            case Types.VARCHAR:
            case Types.LONGVARCHAR:
            case Types.CHAR:
            case Types.BOOLEAN:
                _value = String.valueOf(value);
                break;
            case Types.BINARY:
            case Types.SMALLINT:
            case Types.INTEGER:
            case Types.BIGINT:
            case Types.FLOAT:
            case Types.DOUBLE:
            case Types.NUMERIC:
                _value = Double.parseDouble(value.toString());
            default:
                _value = String.valueOf(value);

        }
        return _value;
    }

    public static String getOpenGraphRequest() {
        return "graph = ConfiguredGraphFactory.open(graphName);";
    }

    public static String getTraversalRequest() {
        return "graph = ConfiguredGraphFactory.open(graphName); g=graph.traversal();";
    }

    public static String getPropertyRequest(String propertyName, int type) {
        String typeString = sqlType2JanusType(type);
        //当前强制都设置为String类型，搜索使用精确匹配
        typeString = "String";
        if (!typeString.equals("")) {
            return String.format("if (!management.containsPropertyKey('%s'))" +
                            "{PropertyKey %s = management.makePropertyKey('%s').dataType(%s.class).make();};",
                    propertyName, propertyName, propertyName, typeString);
        } else {
            return " ";
        }
    }

    public static String getCategoryLabelRequest(String labelName) {
        return String.format("if (!management.containsVertexLabel('%s'))" +
                        "{management.makeVertexLabel('%s').make();};",
                labelName,labelName);
    }

    public static String getEdgeLabelRequest(String labelName) {
        return String.format("if (!management.containsEdgeLabel('%s'))" +
                        "{management.makeEdgeLabel('%s').make();};",
                labelName, labelName);
    }

    public static String getNodeRequest(String labelName, String refId, Map<String, Object> propertyMap) {
        StringBuilder s = new StringBuilder();
        s.append(String.format("g.addV('%s').property('refId','%s')",
                labelName, refId));
        for (Map.Entry<String, Object> entry: propertyMap.entrySet()) {
            s.append(String.format(".property('%s', '%s')", entry.getKey(), entry.getValue().toString()));
        }
        s.append(".iterate();");
        return s.toString();
    }

    public static String getLinkRequest(String labelName, String srcId, String tarId, String refId) {
                return String.format("g" +
                                ".V().has('refId','%s').as('src')" +
                                ".V().has('refId','%s').as('tar')" +
                                ".addE('%s').from('src').to('tar')" +
                                ".property('refId','%s')" +
                                ".iterate();",
                        srcId, tarId, labelName, refId);
    }

    public static String getCompositeIndexRequest(String keyName) {
        return String.format("if (!management.containsGraphIndex('%sIndex'))" +
                        "{" +
                        "PropertyKey %s = management.getPropertyKey('%s');" +
                        "management.buildIndex('%sIndex', Vertex.class).addKey(%s).buildCompositeIndex();" +
                        "}; ",
                keyName, keyName, keyName, keyName, keyName);
    }

    /**
     * param: src, tar, maxStep
     */
    public static String getAllPathRequest() {
        return "g.V().has('refId', src).repeat(__.out()).until(__.has('refId', tar).or().loops().is(maxStep)).simplePath().path().by('refId').dedup().toList();";
    }

    public static String getShortestPathRequest() {
        return "g.V().has('refId', src).store('x')" +
                ".repeat(out().where(without('x'))" +
                ".aggregate('x')).until(has('refId', tar).or().loops().is(maxStep)).limit(1).path()" +
                ".by('refId').toList();";
    }

    /**
     * param: refIdList, maxIter
     */
    public static String getPeerPressureRequest() {
        return "g.V().properties(TraversalVertexProgram.HALTED_TRAVERSERS).drop().iterate(); g.tx().commit();" +
                "g.withComputer().V().has('refId', within(refIdList)).peerPressure()" +
                ".with(PeerPressure.times, maxIter)" +
                ".group().by(PeerPressureVertexProgram.CLUSTER)" +
                ".by('refId').toList();";
    }

    public static String getDropNodeRequest() {
        return "g.V().has('refId', refId).drop().iterate(); g.tx().commit();";
    }

    public static String getDropLinkRequest() {
        return "g.E().has('refId', refId).drop().iterate(); g.tx().commit();";
    }

    public static String getBatchDropNodesAndLinksRequest() {
        return "g.V().has('refId', within(refIdListNode)).drop().iterate(); " +
                "g.E().has('refId', within(refIdListLink)).drop().iterate(); " +
                "g.tx().commit();";
    }

    public static String getSearch4attrNamesRequest(int n) {
        StringBuilder s = new StringBuilder();
        s.append("g.V().or(");
        for (int i = 0; i < n; i++){
            String propertyName = "property" + i;
            String valueName = "value" + i;
            s.append(String.format("has(%s, %s), ", propertyName, valueName));
        }
        s.append(").values('refId').toList();");
        return s.toString();
    }

    public static String getAdjacentWithStepRequest() {
        return "g.V().has('refId', within(refIdList))" +
                ".repeat(both()).times(maxStep)" +
                ".emit()" +
                ".dedup()" +
                ".values('refId')" +
                ".toList();";
    }

    public static String aliasPropertyKey(String key, Long graphId) {
        if (MetricResultManager.tagList.contains(key)) {
            return key;
        }
        return "_" + graphId + "_" +  key + "_";


    }

    public static String removeAliasPropertyKey(String alias, Long graphId) {
        String prefix = "_" + graphId + "_";
        String suffix = "_";
        if (alias.startsWith(prefix)) {
            return alias.substring(prefix.length(), alias.length() - suffix.length());
        }
        return alias;

    }

    public static String aliasNodeId(String id, Long graphId) {
        return "n_" + graphId + "_" +  id;
    }

    public static String aliasLinkId(String id, Long graphId) {
        return "l_" + graphId + "_" +  id;
    }

    public static String removeAliasId(String alias, Long graphId) {
        String prefix = "_" + graphId + "_";
        if (alias.startsWith("n" + prefix)
                || alias.startsWith("l" + prefix)
                || alias.startsWith("c" + prefix)
                || alias.startsWith("e" + prefix)){
            return alias.substring(prefix.length() + 1);
        }
        return alias;
    }

    public static Set<?> parseSetFromStr(String str, int type) {
        //{a,b,c}
        str = "[\"" + str + "\"]";
        Class<?> dataType = sqlType2TypeClass(type);
        List<?> list = JSON.parseArray(str, dataType);
        return new HashSet<>(list);
    }

    public static Double round2DecimalDouble(Double d) {
        DecimalFormat decimalFormat = new DecimalFormat("#0.00");
        return Double.parseDouble(decimalFormat.format(d));
    }


    public static List<String> sortListIgnoreCase(List<String> list) {
        list.sort(new Comparator<String>() {
            @Override
            public int compare(String s1, String s2) {
                return s1.compareToIgnoreCase(s2);
            }
        });
        return list;
    }

    public static <T> T parseJSONObject2Obj(JSONObject obj, String key, Class<T> clazz) {
        T o;
        try {
            o = obj.getJSONObject(key).toJavaObject(clazz);
        } catch (Exception e) {
            throw new GraphAnalysisException(ApiResultCode.PARAM_ERROR,
                    "字段:" + key);
        }
        return o;
    }

    public static <T> List<T> parseJSONArray2List(JSONObject obj, String key, Class<T> clazz) {
        List<T> list;
        try {
            list = obj.getJSONArray(key).toJavaList(clazz);
        } catch (Exception e) {
            throw new GraphAnalysisException(ApiResultCode.PARAM_ERROR,
                    "字段:" + key);
        }
        return list;
    }
 }
